package cn.achao.csr.chat.service.impl;

import cn.achao.csr.api.model.req.ChatMessagePageReq;
import cn.achao.csr.api.model.req.ChatMessageReq;
import cn.achao.csr.api.model.entity.*;
import cn.achao.csr.api.model.enums.NormalOrNoEnum;
import cn.achao.csr.api.model.resp.ChatMessageResp;
import cn.achao.csr.base.constant.RedisKeyConstants;
import cn.achao.csr.base.constant.enums.SysSourceEnum;
import cn.achao.csr.base.enums.WSReqTypeEnum;
import cn.achao.csr.base.exception.BusinessException;
import cn.achao.csr.base.req.WSBaseReq;
import cn.achao.csr.base.resp.CursorPageBaseResp;
import cn.achao.csr.base.resp.WSBaseResp;
import cn.achao.csr.base.utils.IpUtils;
import cn.achao.csr.base.utils.ThrowUtils;
import cn.achao.csr.chat.core.build.MessageAdapter;
import cn.achao.csr.chat.core.build.WSAdapter;
import cn.achao.csr.chat.core.event.MessageSendEvent;
import cn.achao.csr.chat.dao.*;
import cn.achao.csr.chat.service.ChatService;
import cn.achao.csr.chat.service.ChatUserService;
import cn.achao.csr.chat.service.cache.OnlineUserCacheService;
import cn.achao.csr.chat.service.strategy.msg.AbstractMsgHandler;
import cn.achao.csr.chat.service.strategy.msg.MsgHandlerFactory;
import cn.achao.csr.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.websocket.Session;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @Author: achao
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {

    private static final Duration EXPIRE_TIME = Duration.ofHours(1);
    // 存储当前对象
    public static final Map<Long, Session> sessionMap = new ConcurrentHashMap<>();

    private final Environment environment;
    private final WxMpService wxMpService;
    private final RedisService redisService;
    private final ChatUserService chatUserService;
    private final ThreadPoolTaskExecutor taskExecutor;
    private final ApplicationEventPublisher applicationEventPublisher;

    private final ChatMsgDao msgDao;
    private final ChatRoomDao roomDao;
    private final ChatMsgDao chatMsgDao;
    private final ChatMsgMarkDao msgMarkDao;
    private final ChatContactDao contactDao;
    private final ChatRoomGroupDao roomGroupDao;
    private final ChatRoomFriendDao roomFriendDao;
    private final ChatGroupMemberDao groupMemberDao;

    private final OnlineUserCacheService onlineUserCache;

    @Override
    public void onOpen(Session session, Long userId) {
        String chatServerIP = getChatServerIP();
        log.debug("onOpen当前服务 {}", chatServerIP);
        sessionMap.put(userId, session);
        String key = RedisKeyConstants.getKey(RedisKeyConstants.USER_CHAT_SERVER_KEY, userId);
        log.debug("onOpen 缓存用户所在服务器ip {}", key);
        redisService.setCacheObject(key, chatServerIP);

        onlineUserCache.online(userId, new Date());
        chatUserService.online(userId);
    }

    @Override
    public void onClose(Long userId) {
        String chatServerIP = getChatServerIP();
        log.debug("onClose当前服务 {}", chatServerIP);
        sessionMap.remove(userId);
        onlineUserCache.offline(userId, new Date());
        chatUserService.offline(userId);
        redisService.deleteObject(RedisKeyConstants.getKey(RedisKeyConstants.USER_CHAT_SERVER_KEY, userId));
    }

    @Override
    public void onMessage(String message, Long userId) {
        WSBaseReq req = JSON.parseObject(message, WSBaseReq.class);
        WSReqTypeEnum wsReqTypeEnum = WSReqTypeEnum.of(req.getType());
        if (!isExistSession(userId)) {
            return;
        }
        Session session = sessionMap.get(userId);
        switch (wsReqTypeEnum) {
            case LOGIN:
                log.info("请求微信登录二维码");
                handleWxLoginReq(userId,session);
                break;
            case HEARTBEAT:
                break;
            default:
                log.info("未知类型");
        }
    }

    /**
     * 处理微信登录请求
     */
    private void handleWxLoginReq(Long userId, Session session) {

        //请求微信接口，获取登录码地址
        try {
            WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(Math.toIntExact(userId), (int) EXPIRE_TIME.getSeconds());
            WSBaseResp<?> wsBaseResp = WSAdapter.buildLoginResp(wxMpQrCodeTicket);
            session.getBasicRemote().sendText(JSON.toJSONString(wsBaseResp));
        } catch (WxErrorException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional
    public Long sendMsg(ChatMessageReq message, Long userId) {
        check(message, userId);
        AbstractMsgHandler<?> msgHandler = MsgHandlerFactory.getStrategyNoNull(message.getMsgType());
        Long msgId = msgHandler.checkAndSaveMsg(message, userId);
        //发布消息发送事件
        applicationEventPublisher.publishEvent(new MessageSendEvent(this, msgId));
        return msgId;
    }

    @Override
    public ChatMessageResp getMsgResp(ChatMsg message, Long receiveUid) {
        return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid));
    }

    @Override
    public ChatMessageResp getMsgResp(Long msgId, Long receiveUid) {
        ChatMsg msg = msgDao.getById(msgId);
        return getMsgResp(msg, receiveUid);
    }

    /**
     * 私聊
     * @param wsBaseMsg
     * @param uid
     */
    @Override
    public void sendToUid(WSBaseResp<?> wsBaseMsg, Long uid) {
        if (!isExistSession(uid)) {
            log.debug("用户{}不在线或者不在该服务器{}", uid, getChatServerIP());
            return;
        }
        if (sessionMap.containsKey(uid)) {
            Session session = sessionMap.get(uid);
            taskExecutor.execute(() -> sendToClient(session, wsBaseMsg));
        }
    }


    /**
     * 群聊
     * @param wsBaseMsg 发送的消息体
     * @param skipUid    需要跳过的人
     */
    @Override
    public void sendToAllOnline(WSBaseResp<?> wsBaseMsg, Long skipUid) {
        for (Long userId : sessionMap.keySet()) {
            if (skipUid != null && Objects.equals(userId, skipUid)) {
                log.info("此消息{}不发送给{}用户", JSON.toJSONString(wsBaseMsg), userId);
                continue;
            }
            Session session = sessionMap.get(userId);
            taskExecutor.execute(() -> sendToClient(session, wsBaseMsg));
        }
    }

    /**
     * 获取用户房间历史消息
     * @param req
     * @param userId
     * @return
     */
    @Override
    public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq req, Long userId) {
        //用最后一条消息id，来限制被踢出的人能看见的最大一条消息
        Long lastMsgId = getLastMsgId(req.getRoomId(), userId);
        CursorPageBaseResp<ChatMsg> cursorPage = chatMsgDao.getCursorPage(req.getRoomId(), req, lastMsgId);
        if (cursorPage.isEmpty()) {
            return CursorPageBaseResp.empty();
        }
        return CursorPageBaseResp.init(cursorPage, getMsgRespBatch(cursorPage.getList(), userId));
    }

    /**
     * 获取该用户在该房间的最后一条信息id
     * @param roomId
     * @param userId
     * @return
     */
    private Long getLastMsgId(Long roomId, Long userId) {
        ChatRoom room = roomDao.getById(roomId);
        ThrowUtils.throwIf(room == null,
                BusinessException.fail("会话不存在", SysSourceEnum.CSR_CHAT.getSysName()));
        if (room.isHotRoom()) {
            return null;
        }
        ThrowUtils.throwIf(userId == null,
                BusinessException.fail("请先登录", SysSourceEnum.CSR_CHAT.getSysName()));
        ChatContact contact = contactDao.getUserRoomContact(userId, roomId);
        ThrowUtils.throwIf(contact == null,
                BusinessException.fail("用户没有此会话", SysSourceEnum.CSR_CHAT.getSysName()));
        return contact.getLastMsgId();
    }

    /**
     * 发送组装消息给客户端
     * @param session
     * @param wsBaseMsg
     */
    private void sendToClient(Session session, WSBaseResp<?> wsBaseMsg) {
        try {
            session.getBasicRemote().sendText(JSON.toJSONString(wsBaseMsg));
        } catch (IOException e) {
            log.error("发送消息失败", e);
            ThrowUtils.throwIf(BusinessException.fail("发送消息失败", SysSourceEnum.CSR_CHAT.getSysName()));
        }
    }

    /**
     * 获取当前聊天服务器的ip和端口
     * @return
     */
    private String getChatServerIP() {
        String ip = IpUtils.getCurrentServerIP();
        String port = environment.getProperty("server.port");
        return ip + ":" + port;
    }

    /**
     * 判断用户是否存在当前服务器中
     * @param userId
     * @return
     */
    private boolean isExistSession(Long userId) {
        String chatServerIP = getChatServerIP();
        String userServerIP = redisService.getCacheObject(RedisKeyConstants.getKey(RedisKeyConstants.USER_CHAT_SERVER_KEY, userId)).toString();
        return chatServerIP.equals(userServerIP);
    }

    /**
     * 校验
     * @param request
     * @param uid
     */
    private void check(ChatMessageReq request, Long uid) {
        ChatRoom room = roomDao.getById(request.getRoomId());
        ThrowUtils.throwIf(room == null,
                BusinessException.fail("房间不存在", SysSourceEnum.CSR_CHAT.getSysName()));
        if (room.isHotRoom()) {//全员群跳过校验
            return;
        }
        if (room.isRoomFriend()) {
            ChatRoomFriend roomFriend = roomFriendDao.getByRoomId(request.getRoomId());
            ThrowUtils.throwIf(NormalOrNoEnum.NOT_NORMAL.getStatus().equals(roomFriend.getStatus()),
                    BusinessException.fail("您已经被拉黑", SysSourceEnum.CSR_CHAT.getSysName()));
        }
        if (room.isRoomGroup()) {
            ChatRoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
            ChatGroupMember member = groupMemberDao.getMember(roomGroup.getId(), uid);
            ThrowUtils.throwIf(member == null,
                    BusinessException.fail("您已经被移除该群", SysSourceEnum.CSR_CHAT.getSysName()));
        }

    }

    /**
     * 批量组装消息
     * @param messages
     * @param receiveUid
     * @return
     */
    public List<ChatMessageResp> getMsgRespBatch(List<ChatMsg> messages, Long receiveUid) {
        if (CollectionUtil.isEmpty(messages)) {
            return new ArrayList<>();
        }
        //查询消息标志
        List<ChatMsgMark> msgMark = msgMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(ChatMsg::getId).collect(Collectors.toList()));
        return MessageAdapter.buildMsgResp(messages, msgMark, receiveUid);
    }
}
